package calao.plan;

import java.util.Calendar;
import java.util.Comparator;
import java.util.Vector;

import calao.plan.client.Resource;
import calao.plan.client.WorkPeriod;
import calao.util.TimePeriod;

public class Schedule 
implements Comparator<TimePeriod>
{

	final Resource resource;
	final Vector<TimePeriod> busyPeriods = new Vector<TimePeriod>();

	// end of scheduled work
	long end = -1; 
	// work period that contains end date 
	TimePeriod workPeriod;
	// next busy period after or containing end date
	TimePeriod busyPeriod;
	// next busy period index
	int busyPeriodIndex = 0;

	public Schedule(Resource resource)
	{
		this.resource = resource;
	}

	public Schedule(int rid, WorkPeriod[] periods)
	{
		this.resource = new Resource(rid);
		resource.setWorkPeriods(periods);
	}

	/**
	 * Move busyPeriod pointer to the next busy period that contains or follows a date.
	 */
	private boolean nextBusyPeriod(long frameStart)
	{
		while (busyPeriodIndex<busyPeriods.size()) {
			busyPeriod = busyPeriods.get(busyPeriodIndex);
			if (busyPeriod.getEndTime()>frameStart) return true;
			busyPeriodIndex++;
		}
		busyPeriod = null;
		return false;
	}

	/**
	 * Move workPeriod pointer to the next work period in a given timeframe.
	 * Also moves end date to workPeriod start. 
	 */
	private void nextWorkPeriod(long frameStart)
	{
		Calendar calStart = null;
		// iterate over week days
		for (int d=0; d<8; d++) {
			if (calStart==null) {
				// init to frame start
				calStart = Calendar.getInstance();
				calStart.setTimeInMillis(frameStart);
			}
			else {
				// move to 1 o'clock am on next day
				calStart.set(Calendar.HOUR_OF_DAY, 1);
				calStart.add(Calendar.DATE, 1);
			}
			// loop thru work periods
			// NB CalendarDate weekdays start at 0 while Java Calendar weekdays start at 1
			int weekDay = calStart.get(Calendar.DAY_OF_WEEK) - 1;
			for (WorkPeriod period : resource.getWorkPeriods(weekDay)) {
				// find first period on same day that ends after frame start 
				if (lessThan(calStart, period.getEndHour(), period.getEndMin())) {
					// if period starts after frame start
					// then set result start to period start
					if (lessThan(calStart, period.getStartHour(), period.getStartMin())) 
					{
						calStart.set(Calendar.HOUR_OF_DAY, period.getStartHour());
						calStart.set(Calendar.MINUTE, period.getStartMin());
					}
					Calendar calEnd = (Calendar)calStart.clone();
					calEnd.set(Calendar.HOUR_OF_DAY, period.getEndHour());
					calEnd.set(Calendar.MINUTE, period.getEndMin());
					workPeriod = new TimePeriod(calStart.getTimeInMillis(),
							calEnd.getTimeInMillis());
					end = workPeriod.getStartTime();
					return;
				}
			}
		}
	}

	static boolean lessThan(int t1hour, int t1min, int t2hour, int t2min)
	{
		if (t1hour<t2hour) return true;
		if (t1hour>t2hour) return false;
		return t1min<t2min;
	}

	static boolean lessThan(Calendar t1, int t2hour, int t2min)
	{
		return lessThan(t1.get(Calendar.HOUR_OF_DAY), t1.get(Calendar.MINUTE), 
				t2hour, t2min);
	}

	static boolean lessThan(int t1hour, int t1min, Calendar t2)
	{
		return lessThan(t1hour, t1min, 
				t2.get(Calendar.HOUR_OF_DAY), t2.get(Calendar.MINUTE));
	}

	static boolean lessThanOrEq(int t1hour, int t1min, int t2hour, int t2min)
	{
		if (t1hour<t2hour) return true;
		if (t1hour>t2hour) return false;
		return t1min<t2min;
	}

	static boolean lessThanOrEq(Calendar t1, int t2hour, int t2min)
	{
		return lessThanOrEq(t1.get(Calendar.HOUR_OF_DAY), t1.get(Calendar.MINUTE), 
				t2hour, t2min);
	}

	static boolean lessThanOrEq(int t1hour, int t1min, Calendar t2)
	{
		return lessThanOrEq(t1hour, t1min, 
				t2.get(Calendar.HOUR_OF_DAY), t2.get(Calendar.MINUTE));
	}

	public void addBusyPeriod(TimePeriod period)
	{
		if (end!=-1) throw new RuntimeException();
		busyPeriods.add(period);
	}

	/** Schedules work to be done.
	 * Work is scheduled by taking time from available work periods after start date. 
	 * @param workTime work effort in resource minutes.
	 * @return The soonest period of time during which the work can be performed.
	 */
	public TimePeriod scheduleWork(int workTime, long frameStart)
	{
		if (frameStart>end) {
			if (end==-1) java.util.Collections.sort(busyPeriods, this);
			nextWorkPeriod(frameStart);
			nextBusyPeriod(end);
		}
		long requested = workTime*60000;
		long periodStart = -1;
		while (requested>0) {
			// case end is in next busy period
			if (busyPeriod!=null && busyPeriod.contains(end)) {
				// case no more time available after next busy period
				if (busyPeriod.getEndTime()>=workPeriod.getEndTime()) {
					nextWorkPeriod(busyPeriod.getEndTime());
					nextBusyPeriod(workPeriod.getStartTime());
				}
				// case time potentially available after next busy period
				else {
					end = busyPeriod.getEndTime();
					// move to next busy period
					nextBusyPeriod(end);
				}
			}
			// case end is before next busy period, or no more busy period
			else {
				long left = busyPeriod!=null && workPeriod.getEndTime()>busyPeriod.getStartTime()?
						busyPeriod.getStartTime()-end : workPeriod.getEndTime()-end;
				if (periodStart==-1) periodStart = end;
				if (left>=requested) {
					logScheduledWork(end, end+requested);
					end += requested;
					requested = 0;
				}
				else {
					logScheduledWork(end, end+left);
					end += left;
					requested -= left;
				}
			}
			// if end of work period is reached, move to next work period
			if (end==workPeriod.getEndTime()) {
				nextWorkPeriod(end);
				nextBusyPeriod(workPeriod.getStartTime());
			}
		}
		return new TimePeriod(periodStart, end);
	}
	
	boolean isFixed()
	{
		return resource.getWorkPeriods()==null || resource.getWorkPeriods().length==0;
	}

	public int compare(TimePeriod p1, TimePeriod p2) {
		return p1==p2? 0 : p1.getStartTime()<p2.getStartTime()? -1 : 1;
	}
	
	void logScheduledWork(long start, long end)
	{
		/*
		log(DEBUG, "Scheduled work from %1$tF %1$tR to %2$tF %2$tR", 
				start, end);
		 */		
	}

}
